/**
 * Copyright (C) 2008 aileron.cc
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.aileron.wsgi.container.uri;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;

import cc.aileron.commons.utils.XRange;
import cc.aileron.wsgi.WsgiHttpCategory;

import com.google.inject.Singleton;

/**
 * @author Aileron
 */
@Singleton
public class WsgiUriContainerImpl implements WsgiUriContainer
{
    @Override
    public WsgiUriContainerResult get(final String uri,
            final WsgiHttpCategory method,
            final List<String> overrideKeys)
    {
        final String extension = getExtension(uri);
        return getDto(uri, method, extension, overrideKeys);
    }

    @Override
    public void put(final WsgiUriContainerParameter dto)
    {
        final String extension = getExtension(dto.uri);
        final WsgiUriTreeEntry tree = getWsgiUriTreeEntry(dto.uri);
        tree.setId(dto.method, dto.overrideKey, extension, processId);
        dto.uriParameterKeys = getUriParameterKeys(dto.uri);
        dto.id = processId;
        map.put(processId++, dto);
    }

    /**
     * WsgiContainerDto
     * 
     * @param uri
     * @return
     */
    private WsgiUriContainerResult getDto(final String uri,
            final WsgiHttpCategory method,
            final String extension,
            final List<String> overrideKeys)
    {
        final List<String> uriParameters = new ArrayList<String>();
        final String[] uris = uri.split("/");
        final int max = uris.length;

        int i = 0;
        WsgiUriTree current = root;
        while (i < max)
        {
            current = current.get(uris[i++]);
            if (current == null)
            {
                return null;
            }
            if (current.isDynamic())
            {
                uriParameters.add(uris[i - 1]);
            }
        }

        final WsgiUriContainerParameter dto = map.get(getId(current, method,
                extension, overrideKeys));
        if (dto == null)
        {
            return null;
        }

        final WsgiUriContainerResult result = new WsgiUriContainerResult();
        result.id = dto.id;
        result.isThrough = dto.isThrough;
        result.uriParameters = new HashMap<String, String>();
        for (final int ix : XRange.xrange(0, dto.uriParameterKeys.size()))
        {
            result.uriParameters.put(dto.uriParameterKeys.get(ix),
                    uriParameters.get(ix));
        }
        return result;
    }

    /**
     * @param uri
     * @return
     */
    private String getExtension(final String uri)
    {
        final String[] tokens = uri.split("\\.");
        if (tokens.length < 2)
        {
            return "";
        }
        return tokens[tokens.length - 1];
    }

    /**
     * @param uriTree
     * @param method
     * @param extension
     * @param overrideKeys
     * @return
     */
    private Integer getId(final WsgiUriTree uriTree,
            final WsgiHttpCategory method,
            final String extension,
            final List<String> overrideKeys)
    {
        final Map<String, Integer> oMap = uriTree.getId(method, extension);
        for (final String key : overrideKeys)
        {
            final Integer id = oMap.get(key);
            if (id != null)
            {
                return id;
            }
        }
        return oMap.get("");
    }

    /**
     * @param uri
     * @return uriParameterKeys
     */
    private List<String> getUriParameterKeys(final String uri)
    {
        final List<String> result = new ArrayList<String>();
        final Matcher matcher = pattern.matcher(uri);
        while (matcher.find())
        {
            final String value = matcher.group(1);
            result.add(value);
        }
        return result;
    }

    /**
     * @param uri
     * @return WsgiUriTreeEntry
     */
    private WsgiUriTreeEntry getWsgiUriTreeEntry(final String uri)
    {
        final String[] uris = uri.split("/");
        final int max = uris.length;

        int i = 0;
        WsgiUriTreeEntry base = root;
        WsgiUriTreeEntry current = base;
        while (i < max)
        {
            final String uriToken = uris[i++];

            base = current;
            current = current.get(uriToken);
            if (current == null)
            {
                current = dynamicFactory.concreate(uriToken);
                base.set(uriToken, current);
            }
        }
        return current;
    }

    /**
     * map
     */
    private final HashMap<Integer, WsgiUriContainerParameter> map = new HashMap<Integer, WsgiUriContainerParameter>();

    /**
     * pattern
     */
    private final Pattern pattern = Pattern.compile(Pattern.quote("${")
            + "(.*?)" + Pattern.quote("}"));

    /**
     * process-id
     */
    private int processId = 1;

    /**
     * root
     */
    private final WsgiUriTreeEntry root = new WsgiUriTreeImpl("/");

    /**
     * dynamicFactory
     */
    private static WsgiUriTreeFactory dynamicFactory = new WsgiUriTreeFactory()
    {
        @Override
        public WsgiUriTreeEntry concreate(final String name)
        {
            return new WsgiUriTreeImpl(name);
        }
    };

    @Override
    public String toString()
    {
        return new ReflectionToStringBuilder(this).toString();
    }
}